/*LICENSE_BEGIN
 * ******************************************************************
 * Copyright 2006 by the Dainamite Robocup-Team at the
 * DAI-Labor, TU-Berlin (www.dai-labor.de)
 * 
 * Members of Dainamite are:
 * Holger Endert
 * Robert Wetzker
 * Thomas Karbe
 * Axel Heßler
 * Philippe Bütter
 * Felix Brossmann
 * 
 * Licensed under the GNU GPL (General Public License) Free Software 
 * License, Version 2. You may use this file only in compliance with 
 * the License. You may obtain a copy of the License at:
 * 
 * http://www.fsf.org/licensing/licenses/gpl.txt
 * ******************************************************************
LICENSE_END*/
package robocup.component.situations;

import java.util.Arrays;

import robocup.component.DAInamiteConstants;
import robocup.component.SConf;
import robocup.component.actionfactories.BasicActionFactory;
import robocup.component.actions.KickAction;
import robocup.component.geometry.Vektor;
import robocup.component.worldobjects.Ball;
import robocup.component.worldobjects.Player;

/**
 * 
 * The {@link PassEvaluator} tries to find out if a pass to a given point 
 * succeeds without the oppponent intercepting the ball before. More than that
 * it optimizes the result trying to pass through opponents and keep the ball 
 * trajectory as far away from interception as possible. By now this is only 
 * used for the {@link ScoreSituation} when trying to find the best way for 
 * scoring.<br> 
 */
public class PassEvaluator {

	public static double EMPTY_VALUE = Double.MAX_VALUE;

	private Movements movements;

	private Player[] opponents;

	private int maxCycle = DAInamiteConstants.PROPHET_MAX_CYCLES_PASSING;

  /**
   * true if an opponent is near enough to be considered
   */
	private boolean[] relevantOpponents = new boolean[11];

  /**
   * time need by opponents for reacting when passing
   */
	private int[] playerOffset = new int[11];

	private Player passingPlayer = Player.getDummyInstance();

	private Ball ball = Ball.getDummyInstance();

	private Ball dummyBall = Ball.getDummyInstance();

  //the point to pass to
	private Vektor targetPoint = new Vektor();

  /**
   * the max turn angles for all opponents turning 1x or 2x.
   */
	private double[][] maxTurnAngle = new double[11][2];

  /**
   * true if a player is intercepting from the left side
   */
	private boolean[] isInterceptingLeft = new boolean[11];

  /**
   * true if a player is intercepting from the right side
   */
	private boolean[] isInterceptingRight = new boolean[11];

  //max pass deviation to the right (i.e. right goal post)
	private double initAngleRight;

  //max pass deviation to the right (i.e. left goal post)
	private double initAngleLeft;

	/**
	 * A class that is responsible for evaluating the results of different
	 * simulated passes (effectiveness).
	 * 
	 * @param movements - the {@link Movements}
	 * @param opponents -
	 *            an array with all opponent players
	 */
	public PassEvaluator(Movements movements, Player[] opponents) {

		this.movements = movements;
		this.opponents = opponents;
		Arrays.fill(this.relevantOpponents, true);
	}

	/**
	 * set the parameters.
	 * 
	 * @param me -
	 *            self
	 * @param ball -
	 *            ball
	 * @param targetPoint -
	 *            point to pass to
	 * @param playerOffset -
	 *            offset of opponent players (cycles needed for reacting)
	 * @param opponents -
	 *            opponents
	 * @param relevantOpponents -
	 *            relevant opponents are true
	 * @param maxAngleLeft -
	 *            max pass deviation to the left (i.e. angle from pass line to 
   *            left goal post)
	 * @param maxAngleRight -
	 *            max pass deviation to the right (i.e. angle from pass line to 
   *            right goal post)
	 */
	public void setParams(Player me, Ball ball, Vektor targetPoint,
			int[] playerOffset, Player[] opponents,
			boolean[] relevantOpponents, double maxAngleLeft,
			double maxAngleRight) {

		this.passingPlayer.copy(me);
		this.ball.copy(ball);
		this.targetPoint.copy(targetPoint);
		this.opponents = opponents;
		for (int i = 0; i < 11; i++) {
			this.relevantOpponents[i] = relevantOpponents[i];
		}
		this.playerOffset = playerOffset;
		this.initAngleLeft = maxAngleLeft;
		this.initAngleRight = maxAngleRight;
		this.reset();
	}

	/**
	 * reset.
	 * 
	 */
	private void reset() {

		Arrays.fill(this.isInterceptingLeft, false);
		Arrays.fill(this.isInterceptingRight, false);
	}

	/**
	 * main method which determines the pass results.
	 * 
	 * @param results -
	 *            results - the values of this object will be overwritten!!
	 * @return results - (same object as in param)
	 */
	public PassEvaluatorResults evaluate(PassEvaluatorResults results) {

		results.reset();
		results.setInitAngleLeft(this.initAngleLeft);
		results.setInitRightAngle(this.initAngleRight);

		double distanceToTarget = this.ball.getDistance(this.targetPoint);
		double angleToTarget = this.ball.getAngleTo(this.targetPoint);

		// filtering distant players (simple version)
		for (int playerIndex = 0; playerIndex < this.opponents.length; playerIndex++) {
			if (!this.relevantOpponents[playerIndex]) {
				continue;
			} else if (this.opponents[playerIndex].getDistance(this.ball) > 2 * distanceToTarget) {
				this.relevantOpponents[playerIndex] = false;
				continue;
			} else {
				if (Vektor.normalize(angleToTarget
						- this.ball.getAngleTo(this.opponents[playerIndex])) > 0) {
					results.setIsLeft(playerIndex, true);
				} else {
					results.setIsLeft(playerIndex, false);
				}
			}
		}

    //determine the kick action
		KickAction kickToPoint = BasicActionFactory.kickFastest(
				this.passingPlayer, this.ball, this.targetPoint);
		if (kickToPoint == null) {
			results.setNoPassing(true);
			return results;
		}

		// the max turn angle for all opponents (used by movements.*)
		this.setMaxTurnAngle();

		// dummy = ball after kick
		this.dummyBall.copy(this.ball);
		this.dummyBall.predictThisAfterAction(this.passingPlayer, kickToPoint);

		int correctedCycle;
		double angle;
		double distance;

		// traveled ball way length
		double ballWayLength = 0;

		// simulate the kicked ball till it reaches the target and try if 
    // opponents can intercept save the interception data to "results"
		for (int cycle = 1; cycle < this.maxCycle; cycle++) {

			ballWayLength += this.dummyBall.getSpeed().getLength()
					/ SConf.getInstance().BALL_DECAY;

			// no speed, no fun
			if (ballWayLength == 0)
				break;

			// test for all relevant opponents if someone gets the simulated
			// ball
			for (int playerIndex = 0; playerIndex < this.opponents.length; playerIndex++) {
				if (!this.relevantOpponents[playerIndex]) {
					continue;
				} else {
					correctedCycle = Math
							.min(cycle + this.playerOffset[playerIndex],
									this.maxCycle - 1);

					// assuming that the goalie will go to the short post first
					if (this.opponents[playerIndex].isGoalie()
							&& this.ball.getPosition().x * this.targetPoint.x < 0) {
						correctedCycle -= 2;
					}

					// opponent may be tackle dead
					if (correctedCycle < 0) {
						continue;
					}

					distance = this.movements
							.getLeftDistance(
									playerIndex + 11 - 1,
									correctedCycle,
									this.dummyBall.getPosition(),
									this.opponents[playerIndex].getPosition(),
									this.opponents[playerIndex].getBodyDir(),
									this.maxTurnAngle[playerIndex][0],
									this.maxTurnAngle[playerIndex][1],
									this.opponents[playerIndex].isGoalie() ? this.opponents[playerIndex]
											.getCatchDistance()
											: this.opponents[playerIndex]
													.getKickDistance(), 0.95);

					angle = Math.toDegrees(Math.atan(distance / ballWayLength));

					// save results
					results.setAngle(playerIndex, cycle, angle);
					results.setDistance(playerIndex, distance);
					if (results.isFinished()) {
						return results;
					}
				}
			}

			this.dummyBall.extrapolate(1, SConf.getInstance().BALL_DECAY);
			if (ballWayLength > distanceToTarget) {
				break;
			}
		}
		return results;
	}

	/**
	 * set the max turn angles for every player based on the players speed.
	 * (used for efficiency)
	 * 
	 */
	private void setMaxTurnAngle() {

		for (Player p : this.opponents) {
			this.maxTurnAngle[p.getNumber() - 1][0] = p.getMaxTurningAngle1();
			this.maxTurnAngle[p.getNumber() - 1][1] = p.getMaxTurningAngle2();
		}
	}
}
